package com.license.server.service.impl;

import com.license.server.common.ErrorCode;
import com.license.server.constant.LicenseConstant;
import com.license.server.entity.LicenseCheckModel;
import com.license.server.entity.LicenseCreatorParam;
import com.license.server.entity.dto.LicenseCreatorParamDTO;
import com.license.server.exception.ThrowUtils;
import com.license.server.license.AbstractServerInfos;
import com.license.server.license.CustomKeyStoreParam;
import com.license.server.license.LinuxServerInfos;
import com.license.server.license.WindowsServerInfos;
import com.license.server.service.LicenseCreateService;
import de.schlichtherle.license.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.io.File;
import java.io.IOException;
import java.nio.file.Paths;
import java.text.MessageFormat;
import java.time.Duration;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.Date;
import java.util.Properties;
import java.util.prefs.Preferences;

/**
 * ClassName: LicenseCreateServiceImpl
 * Package: com.memory.licenseserver.service.impl
 * Description:
 *
 * @Author Memory
 * @Create 2024/12/20 11:24
 * @Version 1.0
 */
@Service
@Slf4j
public class LicenseCreateServiceImpl implements LicenseCreateService {
    // 证书生成参数
    @Resource
    private LicenseCreatorParam param;

    /**
     * 生成 License 证书
     *
     * @return boolean
     */
    public LicenseContent generateLicense(LicenseCreatorParamDTO param) {
        // 1.校验参数
        Boolean checkResult = this.checkLicense(param);
        ThrowUtils.throwIf(!checkResult, ErrorCode.OPERATION_ERROR, "证书生成参数校验失败");
        // 2.设置证书生成参数
        Boolean setResult = this.setParam(param);
        ThrowUtils.throwIf(!setResult, ErrorCode.OPERATION_ERROR, "证书生成参数设置失败");
        // 3.生成并保存证书
        LicenseContent licenseContent = this.generate();
        ThrowUtils.throwIf(ObjectUtils.isEmpty(licenseContent), ErrorCode.OPERATION_ERROR, "证书生成失败");
        return licenseContent;
    }

    /**
     * 校验参数
     *
     * @param param 参数
     */
    private Boolean checkLicense(LicenseCreatorParamDTO param) {
        // 检查证书主题是否为空
        String subject = param.getSubject();
        ThrowUtils.throwIf(ObjectUtils.isEmpty(subject), ErrorCode.PARAMS_ERROR, "证书主体不能为空");
        // 检查描述是否为空
        String description = param.getDescription();
        ThrowUtils.throwIf(StringUtils.isBlank(description), ErrorCode.PARAMS_ERROR, "证书描述不能为空");
        // 计算失效时间
        int validityDays = param.getValidityDays();
        ThrowUtils.throwIf(ObjectUtils.isEmpty(validityDays), ErrorCode.PARAMS_ERROR, "证书有效期天数不能为空");
        LocalDateTime issuedTime = LocalDateTime.now();
        LocalDateTime expirationTime = issuedTime.plus(Duration.ofDays(validityDays));
        // 检查失效时间是否早于当前时间(理论上不应该发生，除非有错误或特殊情况)
        LocalDateTime now = LocalDateTime.now();
        ThrowUtils.throwIf(expirationTime.isBefore(now) || expirationTime.isEqual(now), ErrorCode.PARAMS_ERROR, "证书失效时间不能早于或等于当前时间");
        // 设置证书生成参数(主题, 描述, 生效时间, 失效时间)
        this.param.setSubject(subject);
        this.param.setDescription(description);
        this.param.setIssuedTime(issuedTime);
        this.param.setExpiryTime(expirationTime);
        // 返回校验结果
        return true;
    }

    /**
     * 设置证书生成参数
     *
     * @param param 参数
     */
    private Boolean setParam(LicenseCreatorParamDTO param) {
        // 获取配置文件
        Properties prop = new Properties();
        try {
            prop.load(LicenseCreator.class.getResourceAsStream("/application.properties"));
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        // 读取配置文件中的参数
        this.param.setPrivateAlias(prop.getProperty("license.private-alias"));
        this.param.setKeyPass(prop.getProperty("license.key-pass"));
        this.param.setStorePass(prop.getProperty("license.store-pass"));
        this.param.setLicensePath(Paths.get(LicenseConstant.USER_DIR, prop.getProperty("license.license-path")).toString());
        this.param.setPrivateKeysStorePath(Paths.get(LicenseConstant.USER_DIR, prop.getProperty("license.private-keys-store-path")).toString());
        this.param.setConsumerType(prop.getProperty("license.consumer-type"));
        this.param.setConsumerAmount(Integer.parseInt(prop.getProperty("license.consumer-amount")));
        // 扩展校验服务器硬件信息
        LicenseCheckModel serverInfos = this.getServerInfos(System.getProperty("os.name"));
        param.setLicenseCheckModel(serverInfos);
        return true;
    }

    /**
     * 扩展校验服务器硬件信息
     *
     * @param osName 操作系统类型
     * @return 硬件信息
     */
    private LicenseCheckModel getServerInfos(String osName) {
        // 操作系统类型
        ThrowUtils.throwIf(StringUtils.isBlank(osName), ErrorCode.PARAMS_ERROR, "操作系统类型不能为空");
        osName = osName.toLowerCase();
        AbstractServerInfos abstractServerInfos = null;
        // 根据不同操作系统类型选择不同的数据获取方法
        if (osName.startsWith("windows")) {
            abstractServerInfos = new WindowsServerInfos();
        } else if (osName.startsWith("linux")) {
            abstractServerInfos = new LinuxServerInfos();
        } else {// 其他服务器类型
            abstractServerInfos = new LinuxServerInfos();
        }
        // 获取服务器硬件信息
        return abstractServerInfos.getServerInfos();
    }

    /**
     * 生成 License 证书
     *
     * @return 生成结果
     */
    public LicenseContent generate() {
        // 1.初始化证书生成参数
        LicenseParam licenseParam = this.initLicenseParam();
        // 2.初始化证书生成正文信息
        LicenseContent licenseContent = this.initLicenseContent();
        // 3.生成证书
        LicenseManager licenseManager = new LicenseManager(licenseParam);
        try {
            licenseManager.store(licenseContent, new File(this.param.getLicensePath()));
            log.info("证书生成成功");
        } catch (Exception e) {
            log.error(MessageFormat.format("证书生成失败：{0}", param), e.getMessage());
        }
        // 4.保存证书

        return licenseContent;
    }

    /**
     * 初始化证书生成参数
     *
     * @return de.schlichtherle.license.LicenseParam
     */
    private LicenseParam initLicenseParam() {
        // 获取用户偏好
        Preferences preferences = Preferences.userNodeForPackage(LicenseCreator.class);
        // 配置私钥存储参数
        KeyStoreParam privateStoreParam = new CustomKeyStoreParam(LicenseCreator.class
                , param.getPrivateKeysStorePath()   // 私钥存储路径
                , param.getPrivateAlias()           // 私钥别名
                , param.getStorePass()              // 存储密码(可能也用于私钥加密)
                , param.getKeyPass());              // 私钥密码(如果与存储密码不同)
        // 设置对证书内容加密的密钥参数
        CipherParam cipherParam = new DefaultCipherParam(param.getStorePass());
        // 返回证书生成参数
        return new DefaultLicenseParam(param.getSubject()
                , preferences
                , privateStoreParam
                , cipherParam);
    }

    /**
     * 设置证书生成正文信息
     *
     * @return de.schlichtherle.license.LicenseContent
     */
    private LicenseContent initLicenseContent() {
        // 设置证书正文信息
        LicenseContent licenseContent = new LicenseContent();
        licenseContent.setHolder(LicenseConstant.DEFAULT_HOLDER_AND_ISSUER);
        licenseContent.setIssuer(LicenseConstant.DEFAULT_HOLDER_AND_ISSUER);
        licenseContent.setSubject(param.getSubject());
        // 转换时间格式
        licenseContent.setIssued(this.convertTimeFormat(param.getIssuedTime()));
        licenseContent.setNotBefore(this.convertTimeFormat(param.getIssuedTime()));
        licenseContent.setNotAfter(this.convertTimeFormat(param.getExpiryTime()));
        licenseContent.setConsumerType(param.getConsumerType());
        licenseContent.setConsumerAmount(param.getConsumerAmount());
        licenseContent.setInfo(param.getDescription());

        // 扩展校验服务器硬件信息
        // licenseContent.setExtra(param.getLicenseCheckModel());
        return licenseContent;
    }

    /**
     * 转换时间格式
     */
    private Date convertTimeFormat(LocalDateTime localDateTime) {
        // 使用系统默认时区将 LocalDateTime 转换为 ZonedDateTime
        ZonedDateTime zonedDateTime = localDateTime.atZone(ZoneId.systemDefault());
        // 将 ZonedDateTime 转换为 Date
        return Date.from(zonedDateTime.toInstant());
    }
}
